package Server;

import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.util.ArrayList;
import java.io.*;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import Client.TileHandler.SplashGUI;
import Client.TileHandler.getMap;

public class EaTGServer extends JFrame {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private JTextArea output;
	private ArrayList<Player> players = new ArrayList<Player>();
	public ArrayList<FieldPlayer> fplayers = new ArrayList<FieldPlayer>();
	public ArrayList<Ammo> ammo = new ArrayList<Ammo>();
	public String mapData = "";
	public String MapFile = "";
	public boolean wait = true;

	private ServerSocket server;
	private int currentPlayer;
	public int currentPlaying = 0;

	public int maxConnection = 10;

	public EaTGServer() {
		super("EaTG Server");
		currentPlayer = 0;
		MapSelector mapLoader = new MapSelector();
		mapLoader.setVisible(true);
		while (wait) {
			try {
				Thread.currentThread();
				Thread.sleep(500);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
		}
		try {
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db;
			db = dbf.newDocumentBuilder();

			String filePath = getClass().getProtectionDomain().getCodeSource()
					.getLocation().getPath()
					+ "Maps/".replace("%20", " ");
			LoadXMLMap(filePath + MapFile);
			// set up ServerSocket
			try {
				server = new ServerSocket(5000, 2);
			} catch (IOException e) {
				e.printStackTrace();
				System.exit(1);
			}

			output = new JTextArea();
			getContentPane().add(new JScrollPane(output), BorderLayout.CENTER);
			output.setText("Server awaiting connections\n");

			setSize(300, 300);
			setVisible(true);
		} catch (ParserConfigurationException e1) {
		}
	}

	public String getFileText(String path) {
		File file = new File(path);
		StringBuffer contents = new StringBuffer();
		BufferedReader reader = null;

		try {
			reader = new BufferedReader(new FileReader(file));
			String text = null;
			while ((text = reader.readLine()) != null) {
				contents.append(text).append(
						System.getProperty("line.separator"));
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (reader != null) {
					reader.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return contents.toString();
	}

	public void LoadXMLMap(String path) {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document doc = builder.parse(path);
			doc.getDocumentElement().normalize();
			int tileLength = doc.getElementsByTagName("tile").getLength();
			int spriteWidth = Integer.parseInt(doc.getDocumentElement()
					.getAttribute("width"));
			int spriteHeight = Integer.parseInt(doc.getDocumentElement()
					.getAttribute("height"));
			ArrayList<String> tiles = new ArrayList<String>();
			for (int tilenum = 0; tilenum < tileLength; tilenum++) {
				Element tilel = (Element) doc.getElementsByTagName("tile")
						.item(tilenum);
				tiles.add(tilel.getAttribute("gid"));
			}

			String tilesS = "";
			boolean first = true;
			for (String tile:tiles){
				if (!first){
					tilesS+=",";
				}
				tilesS+= tile;
				first = false;
			}
			mapData = tileLength + ":" + spriteWidth + ":" + spriteHeight + ":" + tilesS;
			System.out.println(mapData);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public class MapSelector extends javax.swing.JFrame {

		/** Creates new form MapSelector */
		public MapSelector() {
			initComponents();
		}

		/**
		 * This method is called from within the constructor to initialize the
		 * form. WARNING: Do NOT modify this code. The content of this method is
		 * always regenerated by the Form Editor.
		 */
		@SuppressWarnings("unchecked")
		// <editor-fold defaultstate="collapsed" desc="Generated Code">
		private void initComponents() {

			jScrollPane1 = new javax.swing.JScrollPane();
			jList1 = new javax.swing.JList();
			jButton1 = new javax.swing.JButton();
			jButton2 = new javax.swing.JButton();
			setResizable(false);
			setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
			setTitle("Map Selector");

			String MapsDirectory = (getClass().getProtectionDomain()
					.getCodeSource().getLocation().getPath() + "Maps/")
					.replace("%20", " ");
			File folder = new File(MapsDirectory);
			File[] listOfFiles = folder.listFiles();

			final ArrayList<String> files = new ArrayList<String>();
			for (File file : listOfFiles) {
				if (file.getName().toString().endsWith(".tmx")) {
					files.add(file.getName().replace(".tmx", ""));
				}
			}
			jList1.setModel(new javax.swing.AbstractListModel() {

				ArrayList<String> strings = files;

				public int getSize() {
					return strings.size();
				}

				public Object getElementAt(int i) {
					return strings.get(i);
				}
			});
			jScrollPane1.setViewportView(jList1);

			jButton1.setText("Select");
			jButton1.addActionListener(new java.awt.event.ActionListener() {

				public void actionPerformed(java.awt.event.ActionEvent evt) {
					jButton1ActionPerformed(evt);
				}
			});

			jButton2.setText("Cancel");
			jButton2.addActionListener(new java.awt.event.ActionListener() {

				public void actionPerformed(java.awt.event.ActionEvent evt) {
					jButton2ActionPerformed(evt);
				}
			});

			javax.swing.GroupLayout layout = new javax.swing.GroupLayout(
					getContentPane());
			getContentPane().setLayout(layout);
			layout.setHorizontalGroup(layout
					.createParallelGroup(
							javax.swing.GroupLayout.Alignment.LEADING)
					.addGroup(
							layout.createSequentialGroup()
									.addContainerGap()
									.addGroup(
											layout.createParallelGroup(
													javax.swing.GroupLayout.Alignment.TRAILING)
													.addComponent(
															jScrollPane1,
															javax.swing.GroupLayout.Alignment.LEADING,
															javax.swing.GroupLayout.DEFAULT_SIZE,
															234,
															Short.MAX_VALUE)
													.addGroup(
															javax.swing.GroupLayout.Alignment.LEADING,
															layout.createSequentialGroup()
																	.addComponent(
																			jButton1,
																			javax.swing.GroupLayout.PREFERRED_SIZE,
																			114,
																			javax.swing.GroupLayout.PREFERRED_SIZE)
																	.addPreferredGap(
																			javax.swing.LayoutStyle.ComponentPlacement.RELATED)
																	.addComponent(
																			jButton2,
																			javax.swing.GroupLayout.PREFERRED_SIZE,
																			114,
																			javax.swing.GroupLayout.PREFERRED_SIZE)))
									.addContainerGap()));
			layout.setVerticalGroup(layout
					.createParallelGroup(
							javax.swing.GroupLayout.Alignment.LEADING)
					.addGroup(
							javax.swing.GroupLayout.Alignment.TRAILING,
							layout.createSequentialGroup()
									.addContainerGap()
									.addComponent(
											jScrollPane1,
											javax.swing.GroupLayout.PREFERRED_SIZE,
											190,
											javax.swing.GroupLayout.PREFERRED_SIZE)
									.addPreferredGap(
											javax.swing.LayoutStyle.ComponentPlacement.RELATED,
											javax.swing.GroupLayout.DEFAULT_SIZE,
											Short.MAX_VALUE)
									.addGroup(
											layout.createParallelGroup(
													javax.swing.GroupLayout.Alignment.BASELINE)
													.addComponent(jButton1)
													.addComponent(jButton2))
									.addContainerGap()));

			pack();
		}// </editor-fold>

		private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
			MapFile = jList1.getSelectedValue().toString() + ".tmx";
			wait = false;
			dispose();
		}

		private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
			wait = false;
			dispose();
		}

		/**
		 * @param args
		 *            the command line arguments
		 */
		// Variables declaration - do not modify
		private javax.swing.JButton jButton1;
		private javax.swing.JButton jButton2;
		private javax.swing.JList jList1;
		private javax.swing.JScrollPane jScrollPane1;
		// End of variables declaration
	}

	// wait for two connections so game can be played
	public void execute() {
		try {

			while (true) {
				Socket connection = server.accept();
				// Check to see if maximum reached
				if (currentPlaying > maxConnection - 1) {
					// Kill the connection
					PrintStream pout = new PrintStream(
							connection.getOutputStream());
					pout.println("Too many users");
					display("Connection refused because max players are on");
					connection.close();
				} else {
					players.add(currentPlayer, new Player(connection, this,
							currentPlayer));
					players.get(currentPlayer).start();
					currentPlayer++;
				}
			}

		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}

		// Player X is suspended until Player O connects.
		// Resume player X now.
		/*
		 * synchronized ( players[ 0 ] ) { players[ 0 ].threadSuspended = false;
		 * players[ 0 ].notify(); }
		 */

	}

	public void display(String s) {
		output.append(s + "\n");
	}

	public boolean gameOver() {
		// Place code here to test for a winner of the game
		return false;
	}

	public static void main(String args[]) {
		EaTGServer game = new EaTGServer();

		game.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});

		game.execute();
	}

	public FieldPlayer containsPlayID(int id) {
		for (FieldPlayer p : fplayers) {
			if (p.getId() == id) {
				return p;
			}
		}
		return null;
	}

	// "ammo:" + a.getStartLoc().x + "x" + a.getStartLoc().y + ":" +
	// a.getAngle() + ":" + a.getType()

	public void parseAmmo(String input, int num) {
		String[] ins = input.split(":");

		String[] locS = ins[1].split("x");
		Point locL = new Point(Integer.parseInt(locS[0]),
				Integer.parseInt(locS[1]));
		Ammo aa = new Ammo();
		aa.setStartLoc(locL);
		aa.setShooter(num);
		aa.setAngle(Double.parseDouble(ins[2]));
		aa.setType(Integer.parseInt(ins[3]));
		ammo.add(aa);
	}

	public void parsePlayer(String input, int num) {
		String[] ins = input.split(":");
		if (containsPlayID(num) == null) {
			String[] locS = ins[3].split("x");
			Point locL = new Point(Integer.parseInt(locS[0]),
					Integer.parseInt(locS[1]));
			fplayers.add(new FieldPlayer(num, "Player " + num, locL, Integer
					.parseInt(ins[2])));
		} else {
			String[] locS = ins[3].split("x");
			Point locL = new Point(Integer.parseInt(locS[0]),
					Integer.parseInt(locS[1]));
			for (FieldPlayer p : fplayers) {
				if (p.getId() == num) {
					p.Set(num, "Player " + num, locL, Integer.parseInt(ins[2]),
							Double.parseDouble(ins[4]));
				}
			}
		}
	}
}

// Player class to manage each Player as a thread
class Player extends Thread {
	private Socket connection;
	private BufferedReader input;
	private PrintWriter output;
	private EaTGServer control;
	private int number;
	protected boolean threadSuspended = true;

	public Player(Socket s, EaTGServer t, int num) {

		connection = s;

		// Set a timeout of 'timeout_length' milliseconds
		try {
			connection.setSoTimeout(3000);
		} catch (SocketException se) {
			System.err.println("Unable to set socket option SO_TIMEOUT");
		}

		try {
			/*
			 * input = new DataInputStream( connection.getInputStream() );
			 * output = new DataOutputStream( connection.getOutputStream() );
			 */
			// BufferedReader stdIn = new BufferedReader(new
			// InputStreamReader(System.in));
			input = new BufferedReader(new InputStreamReader(
					connection.getInputStream()));
			output = new PrintWriter(connection.getOutputStream(), true);
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}

		control = t;
		number = num;
	}

	public String generatePlayerOutput(int ignoreID) {
		// "players;57,LolTest,10:20`58,TesetLol,20:10"
		String base = "players;";
		int max = control.fplayers.size();
		int count = 0;
		for (FieldPlayer fp : control.fplayers) {
			if (fp.getId() == ignoreID)
				continue; // TODO check if player is speed hacking and reset
							// position
			base = base + fp.getId() + "," + fp.getName() + "," + fp.getLoc().x
					+ ":" + fp.getLoc().y + "," + fp.getLatency();
			if (count < max)
				base = base + "`";
			count++;
		}
		if (count == 0)
			return null;
		return base;
	}

	public String generateAmmoOutput() {
		// "players;57,LolTest,10:20`58,TesetLol,20:10"
		String base = "ammo;";
		int max = control.ammo.size();
		int count = 0;
		for (Ammo am : control.ammo) {
			// TODO aimbot prevention? idk lol
			// TODO handle destroying and generating ammo
			base = base + "make," + am.getAmmoID() + "," + am.getStartLoc().x
					+ ":" + am.getStartLoc().y + "," + am.getAngle() + ","
					+ am.getDistance();
			if (count < max)
				base = base + "`";
			count++;
		}
		if (count == 0)
			return null;
		return base;
	}

	public void removeByID(int id) {
		// "players;57,LolTest,10:20`58,TesetLol,20:10"
		for (int i = 0; i < control.fplayers.size(); i++) {
			if (control.fplayers.get(i).getId() == id) {
				control.fplayers.remove(i);
			}
		}
	}

	public void updatePing(int id, long ping) {
		// "players;57,LolTest,10:20`58,TesetLol,20:10"
		for (int i = 0; i < control.fplayers.size(); i++) {
			if (control.fplayers.get(i).getId() == id) {
				control.fplayers.get(i).setLatency(ping);
			}
		}
	}

	public void run() {
		boolean done = false;
		long lastPing = 0;

		try {
			control.display("Player " + number + " connected");
			output.println(number + ":" + control.mapData);
			control.currentPlaying++;

			// Play game
			try {
				while (!done && !connection.isClosed()) {
					String location = input.readLine();

					if (location.equals("pong")) {
						long ping = System.currentTimeMillis() - lastPing;
						updatePing(number, ping);
					}

					// control.display( "From " + number + ": " + location);
					if (location.startsWith("player:")) {
						control.parsePlayer(location, number);
					} else if (location.startsWith("ammo:")) {
						control.parseAmmo(location, number);
					}

					String op = generatePlayerOutput(number);
					if (op != null)
						output.println(op);
					op = generateAmmoOutput();
					if (op != null)
						output.println(op);

					if (!location.equals("pong")) {
						output.println("ping");
						lastPing = System.currentTimeMillis();
					}

					if (control.gameOver())
						done = true;
				}
			} catch (InterruptedIOException iioe) {
				control.display("Player " + number + " timeout");
				control.currentPlaying--;
			} catch (IOException e) {
				control.display("Player " + number
						+ " disconnect or IOException");
				control.currentPlaying--;
			}

			removeByID(number);
			connection.close();
		}

		catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}

	}
}